/**
 * $RCSfile: DefaultConnectionProvider.java,v $
 */

package com.yinbo.database;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.logicalcobwebs.proxool.ConnectionPoolDefinitionIF;
import org.logicalcobwebs.proxool.ProxoolException;
import org.logicalcobwebs.proxool.ProxoolFacade;
import org.logicalcobwebs.proxool.admin.SnapshotIF;

/**
 * 默认的PRXOOLS 的连接池提供.
 * 
 * @author Y.N
 */
public class DefaultConnectionProvider implements ConnectionProvider {
	private final Log Log = LogFactory.getLog(DefaultConnectionProvider.class);

	private Properties settings;
	private String driver;
	private String serverURL;
	private String proxoolURL;
	private String username;
	private String password;
	private int minConnections = 9;
	private int maxConnections = 60;
	private int activeTimeout = 900000; // 15 minutes in milliseconds
	private String testSQL = "";
	private Boolean testBeforeUse = false;
	private Boolean testAfterUse = false;

	/**
	 * Maximum time a connection can be open before it's reopened (in days)
	 * 两次连接之间的最大间隔时间
	 */
	private double connectionTimeout = 0.5;

	/**
	 */
	private boolean mysqlUseUnicode;

	/**
	 * Creates a new DefaultConnectionProvider.
	 */
	public DefaultConnectionProvider() {
		try {
			loadProperties();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public boolean isPooled() {
		return true;
	}

	public Connection getConnection() throws SQLException {
		try {
			Class.forName("org.logicalcobwebs.proxool.ProxoolDriver");
			return DriverManager.getConnection(proxoolURL, settings);
		} catch (ClassNotFoundException e) {
			throw new SQLException(
					"DbConnectionProvider: Unable to find driver: " + e);
		}
	}

	public void start() {
		proxoolURL = "proxool.jcy:" + getDriver() + ":" + getServerURL();
		settings = new Properties();
		settings.setProperty("proxool.maximum-activetime", Integer.toString(activeTimeout));
		settings.setProperty("proxool.maximum-connection-count", Integer.toString(getMaxConnections()));
		settings.setProperty("proxool.minimum-connection-count", Integer.toString(getMinConnections()));
		settings.setProperty("proxool.maximum-connection-lifetime", Integer.toString((int) (86400000 * getConnectionTimeout())));
		settings.setProperty("user", getUsername());
		settings.setProperty("password", (getPassword() != null ? getPassword() : ""));
	}

	public void restart() {
		destroy();
		start();
	}

	public void destroy() {
		settings = null;
	}

	/**
	 * Returns the JDBC driver classname used to make database connections. For
	 * example: com.mysql.jdbc.Driver
	 * 
	 * @return the JDBC driver classname.
	 */
	public String getDriver() {
		return driver;
	}

	/**
	 * Sets the JDBC driver classname used to make database connections. For
	 * example: com.mysql.jdbc.Driver
	 * 
	 * @param driver
	 *            the fully qualified JDBC driver name.
	 */
	public void setDriver(String driver) {
		this.driver = driver;
	}

	/**
	 * Returns the JDBC connection URL used to make database connections.
	 * 
	 * @return the JDBC connection URL.
	 */
	public String getServerURL() {
		return serverURL;
	}

	/**
	 * Sets the JDBC connection URL used to make database connections.
	 * 
	 * @param serverURL
	 *            the JDBC connection URL.
	 */
	public void setServerURL(String serverURL) {
		this.serverURL = serverURL;
	}

	/**
	 * Returns the username used to connect to the database. In some cases, a
	 * username is not needed so this method will return null.
	 * 
	 * @return the username used to connect to the datbase.
	 */
	public String getUsername() {
		return username;
	}

	/**
	 * Sets the username used to connect to the database. In some cases, a
	 * username is not needed so null should be passed in.
	 * 
	 * @param username
	 *            the username used to connect to the database.
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 * Returns the password used to connect to the database. In some cases, a
	 * password is not needed so this method will return null.
	 * 
	 * @return the password used to connect to the database.
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * Sets the password used to connect to the database. In some cases, a
	 * password is not needed so null should be passed in.
	 * 
	 * @param password
	 *            the password used to connect to the database.
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * Returns the minimum number of connections that the pool will use. This
	 * should probably be at least three.
	 * 
	 * @return the minimum number of connections in the pool.
	 */
	public int getMinConnections() {
		return minConnections;
	}

	/**
	 * Sets the minimum number of connections that the pool will use. This
	 * should probably be at least three.
	 * 
	 * @param minConnections
	 *            the minimum number of connections in the pool.
	 */
	public void setMinConnections(int minConnections) {
		this.minConnections = minConnections;
	}

	/**
	 * Returns the maximum number of connections that the pool will use. The
	 * actual number of connections in the pool will vary between this value and
	 * the minimum based on the current load.
	 * 
	 * @return the max possible number of connections in the pool.
	 */
	public int getMaxConnections() {
		return maxConnections;
	}

	/**
	 * Sets the maximum number of connections that the pool will use. The actual
	 * number of connections in the pool will vary between this value and the
	 * minimum based on the current load.
	 * 
	 * @param maxConnections
	 *            the max possible number of connections in the pool.
	 */
	public void setMaxConnections(int maxConnections) {
		this.maxConnections = maxConnections;
	}

	/**
	 * Returns the amount of time between connection recycles in days. For
	 * example, a value of .5 would correspond to recycling the connections in
	 * the pool once every half day.
	 * 
	 * @return the amount of time in days between connection recycles.
	 */
	public double getConnectionTimeout() {
		return connectionTimeout;
	}

	/**
	 * Sets the amount of time between connection recycles in days. For example,
	 * a value of .5 would correspond to recycling the connections in the pool
	 * once every half day.
	 * 
	 * @param connectionTimeout
	 *            the amount of time in days between connection recycles.
	 */
	public void setConnectionTimeout(double connectionTimeout) {
		this.connectionTimeout = connectionTimeout;
		saveProperties();
	}

	/**
	 * Returns the SQL statement used to test if a connection is valid.
	 * 
	 * @return the SQL statement that will be run to test a connection.
	 */
	public String getTestSQL() {
		return testSQL;
	}

	/**
	 * Sets the SQL statement used to test if a connection is valid. House
	 * keeping and before/after connection tests make use of this. This should
	 * be something that causes the minimal amount of work by the database
	 * server and is as quick as possible.
	 * 
	 * @param testSQL
	 *            the SQL statement that will be run to test a connection.
	 */
	public void setTestSQL(String testSQL) {
		this.testSQL = testSQL;
	}

	/**
	 * Returns whether returned connections will be tested before being handed
	 * over to be used.
	 * 
	 * @return True if connections are tested before use.
	 */
	public Boolean getTestBeforeUse() {
		return testBeforeUse;
	}

	/**
	 * Sets whether connections will be tested before being handed over to be
	 * used.
	 * 
	 * @param testBeforeUse
	 *            True or false if connections are to be tested before use.
	 */
	public void setTestBeforeUse(Boolean testBeforeUse) {
		this.testBeforeUse = testBeforeUse;
	}

	/**
	 * Returns whether returned connections will be tested after being returned
	 * to the pool.
	 * 
	 * @return True if connections are tested after use.
	 */
	public Boolean getTestAfterUse() {
		return testAfterUse;
	}

	/**
	 * Sets whether connections will be tested after being returned to the pool.
	 * 
	 * @param testAfterUse
	 *            True or false if connections are to be tested after use.
	 */
	public void setTestAfterUse(Boolean testAfterUse) {
		this.testAfterUse = testAfterUse;
	}

	public boolean isMysqlUseUnicode() {
		return mysqlUseUnicode;
	}

	/**
	 * Load properties that already exist from db.properties.
	 * 
	 * @throws Exception
	 */
	private void loadProperties() throws Exception {

		ClassLoader cl = getClass().getClassLoader();
		InputStream in;

		String name = "/com/yinbo/database/db.cfg";
		if (cl != null) {
			in = cl.getResourceAsStream(name);
		} else {
			in = ClassLoader.getSystemResourceAsStream(name);
		}

		if (in == null) {
			throw new Exception("ConnectionPool configuration file '" + name
					+ "' not found");
		} else {
			try {
				Properties JDBCProperties = new Properties();
				JDBCProperties.load(in);
				driver = JDBCProperties.getProperty("driver");
				serverURL = JDBCProperties.getProperty("serverURL");
				username = JDBCProperties.getProperty("username");
				password = JDBCProperties.getProperty("password");
			} finally {
				// Always close the input stream
				if (in != null) {
					try {
						in.close();
					} catch (Exception ex) {
					}
				}
			}
		}
	}

	/**
	 * Save properties as properties.
	 */
	private void saveProperties() {
		//ignore
	}

	public String toString() {
		try {
			ConnectionPoolDefinitionIF poolDef = ProxoolFacade.getConnectionPoolDefinition("jcy");
			SnapshotIF poolStats = ProxoolFacade.getSnapshot("jcy", true);
			return poolDef.getMinimumConnectionCount() + ","
					+ poolDef.getMaximumConnectionCount() + ","
					+ poolStats.getAvailableConnectionCount() + ","
					+ poolStats.getActiveConnectionCount();
		} catch (ProxoolException e) {
			return "Default Connection Provider";
		}
	}
}
